#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 61DUke.

# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Readme Document for more details.
# Create: 2020-12-27

from PIL import Image
from colormath.color_diff import delta_e_cie2000
from colormath.color_objects import LabColor, sRGBColor
from colormath.color_conversions import convert_color
from interval import Interval

__all__ = [
    "colour_ratio",
    "colour_dominant",
    "colour_similarity"
]


def _sort_by_rgb(colors_tuple):
    """
    colors_tuple contains color count and color RGB, sort the tuple by RGB

    :param colors_tuple:
    :return:
    """
    sorted_tuple = sorted(colors_tuple, key=lambda x: x[1])
    return sorted_tuple


def _rgb_maximum(colors_tuple):
    """
    colors_r max min
    colors_g max min
    colors_b max min

    :param colors_tuple:
    :return:
    """

    r_sorted_tuple = sorted(colors_tuple, key=lambda x: x[1][0])
    g_sorted_tuple = sorted(colors_tuple, key=lambda x: x[1][1])
    b_sorted_tuple = sorted(colors_tuple, key=lambda x: x[1][2])

    r_min = r_sorted_tuple[0][1][0]
    g_min = g_sorted_tuple[0][1][1]
    b_min = b_sorted_tuple[0][1][2]

    r_max = r_sorted_tuple[len(colors_tuple) - 1][1][0]
    g_max = g_sorted_tuple[len(colors_tuple) - 1][1][1]
    b_max = b_sorted_tuple[len(colors_tuple) - 1][1][2]

    return {
        "r_max": r_max,
        "r_min": r_min,
        "g_max": g_max,
        "g_min": g_min,
        "b_max": b_max,
        "b_min": b_min,
        "r_dvalue": (r_max - r_min) / 3,
        "g_dvalue": (g_max - g_min) / 3,
        "b_dvalue": (b_max - b_min) / 3
    }


def _group_by_accuracy(sorted_tuple):
    """
    group the colors by the accuaracy was given
    the R G B colors will be depart to accuracy parts

    :param sorted_tuple:
    :return:
    """
    rgb_maximum_json = _rgb_maximum(sorted_tuple)
    r_min = rgb_maximum_json["r_min"]
    g_min = rgb_maximum_json["g_min"]
    b_min = rgb_maximum_json["b_min"]
    r_dvalue = rgb_maximum_json["r_dvalue"]
    g_dvalue = rgb_maximum_json["g_dvalue"]
    b_dvalue = rgb_maximum_json["b_dvalue"]

    rgb = [
        [[[], [], []], [[], [], []], [[], [], []]],
        [[[], [], []], [[], [], []], [[], [], []]],
        [[[], [], []], [[], [], []], [[], [], []]]
    ]

    for color_tuple in sorted_tuple:
        r_tmp_i = color_tuple[1][0]
        g_tmp_i = color_tuple[1][1]
        b_tmp_i = color_tuple[1][2]
        r_idx = 0 if r_tmp_i < (r_min + r_dvalue) else 1 if r_tmp_i < (r_min + r_dvalue * 2) else 2
        g_idx = 0 if g_tmp_i < (g_min + g_dvalue) else 1 if g_tmp_i < (g_min + g_dvalue * 2) else 2
        b_idx = 0 if b_tmp_i < (b_min + b_dvalue) else 1 if b_tmp_i < (b_min + b_dvalue * 2) else 2
        rgb[r_idx][g_idx][b_idx].append(color_tuple)

    return rgb


def _get_weighted_mean(grouped_image_color):
    """
    calculate every group's weighted mean

        r_weighted_mean = sigma(r * count) / sigma(count)
        g_weighted_mean = sigma(g * count) / sigma(count)
        b_weighted_mean = sigma(b * count) / sigma(count)

    :param grouped_image_color:
    :return:
    """
    sigma_count = 0
    sigma_r = 0
    sigma_g = 0
    sigma_b = 0

    for item in grouped_image_color:
        sigma_count += item[0]
        sigma_r += item[1][0] * item[0]
        sigma_g += item[1][1] * item[0]
        sigma_b += item[1][2] * item[0]

    r_weighted_mean = int(sigma_r / sigma_count)
    g_weighted_mean = int(sigma_g / sigma_count)
    b_weighted_mean = int(sigma_b / sigma_count)

    weighted_mean = (sigma_count, (r_weighted_mean, g_weighted_mean, b_weighted_mean))
    return weighted_mean


def _get_thumbnail(image, size=(256, 256)):
    """
    image thumbnail, reduce the calculate time

    :param image:
    :param size: default
    :return:
    """
    image.thumbnail(size)
    return image


def colour_ratio(image_path: str):
    """
    grabbing representative color palette from an image

    :param image_path:
    :return: list, each tuple item, (ratio, (r, g, b)), sorted

    Functions:
    ---------
        Grab the representative color palette from a image.

    Application:
    ---------
        图像色彩搭配
        室内设计效果图颜色配比

    Example:
    ---------
        >>> palette = colour_ratio(image_path="imgs/IMG_2229.JPG")
        >>> print(palette)
        >>> [(0.51, (35, 18, 26)), (0.19, (121, 41, 59)), (0.09, (142, 129, 110)), (0.07, (209, 129, 118)), (0.05, (214, 35, 62)), (0.04, (131, 102, 77)), (0.02, (225, 42, 115)), (0.02, (207, 188, 148))]

    """
    if not isinstance(image_path, str) or not image_path:
        raise ValueError("image_path param must be string!")

    image = Image.open(image_path)
    # convert non-RGB mode to RGB mode
    if image.mode != "RGB":
        image = image.convert("RGB")

    thumbnail = _get_thumbnail(image)

    # calculate the max colors the image cound have
    # if the color is different in every pixel, the color counts may be the max.
    image_height = thumbnail.height
    image_width = thumbnail.width
    max_colors = image_height * image_width

    image_colors = image.getcolors(max_colors)

    # sort the image colors tuple
    sorted_image_colors = _sort_by_rgb(image_colors)

    # group the colors by the accuracy
    grouped_image_colors = _group_by_accuracy(sorted_image_colors)

    # get the weighted mean of all colors
    colors_mean = []
    for i in range(3):
        for j in range(3):
            for k in range(3):
                grouped_image_color = grouped_image_colors[i][j][k]
                if 0 != len(grouped_image_color):
                    color_mean = _get_weighted_mean(grouped_image_color)
                    colors_mean.append(color_mean)

    # return the most 8 colors
    temp_sorted_colors_mean = sorted(colors_mean)
    if 8 < len(temp_sorted_colors_mean):
        colors_mean = temp_sorted_colors_mean[len(temp_sorted_colors_mean) - 8: len(temp_sorted_colors_mean)]
    else:
        colors_mean = temp_sorted_colors_mean

    # sort the colors_mean
    colors_mean = sorted(colors_mean, reverse=True)

    # get the palette
    palette_tmp = []
    count_sum = 0
    for c_m in colors_mean:
        count_sum += c_m[0]
        palette_tmp.append(c_m)

    # calculate the percentage
    palette = []
    for p in palette_tmp:
        pp = '%.2f' % (p[0] / count_sum)
        tp = (float(pp), p[1])
        palette.append(tp)

    return palette


def colour_dominant(image_path: str):
    """
    grabbing the dominant color from an image

    :param image_path:
    :return: tuple, (ratio, (r, g, b))

    Functions:
    ---------
        Grab the dominant color from a image.

    Application:
    ---------
        图像色彩搭配
        室内设计效果图颜色配比

    Example:
    ---------
        >>> dominant = colour_dominant(image_path="imgs/IMG_2229.JPG")
        >>> print(dominant)
        >>> (0.51, (35, 18, 26))
    """
    if not isinstance(image_path, str) or not image_path:
        raise ValueError("image_path param must be string!")

    palette = colour_ratio(image_path)
    if not palette:
        raise ValueError("Strange, the image has no color？")
    return palette[0]


def colour_similarity(base_rgb: tuple,
                      compare_rgb: tuple
                      ):
    """
    Research Article  Color Image Quality Assessment Based on CIEDE2000

    Combining the color diﬀerence formula of CIEDE2000 and
    the printing industry standard for visual veriﬁcation,
    we present an objective color image quality assessment method
    correlated with subjective vision perception.
    An objective score conformed to subjective perception (OSCSP) Q was
    proposed to directly reﬂect the subjective visual perception.
    In addition, we present a general method to calibrate correction factors
    of color diﬀerence formula under real experimental conditions.
    Our experiment results show that the present DE2000-based metric can be
    consistent with human visual system in general application environment.


    :param base_rgb:
    :param compare_rgb:
    :return: float in [0, 1]

    Functions:
    ---------
        Color contrast, normalized similarity, The highest similarity is 1.

    Application:
    ---------
        图像色彩对比
        室内设计效果图颜色相似度

    Example:
    ---------
        >>> similarity = colour_similarity(base_rgb=(255, 0, 0), compare_rgb=(255, 5, 50))
        >>> print(similarity)
        >>> 0.4480129357712845
    """
    if not isinstance(base_rgb, tuple) or len(base_rgb) != 3:
        raise ValueError("base_rgb param must be 3 tuple!")
    if not isinstance(compare_rgb, tuple) or len(compare_rgb) != 3:
        raise ValueError("compare_rgb param must be 3 tuple!")
    base_lab = convert_color(sRGBColor(
        rgb_r=base_rgb[0],
        rgb_g=base_rgb[1],
        rgb_b=base_rgb[2],
        is_upscaled=True
    ), LabColor)
    compare_lab = convert_color(sRGBColor(
        rgb_r=compare_rgb[0],
        rgb_g=compare_rgb[1],
        rgb_b=compare_rgb[2],
        is_upscaled=True
    ), LabColor)
    delta_e = delta_e_cie2000(base_lab, compare_lab)
    if delta_e in Interval(0.0, 0.5, upper_closed=False):
        Q = 5.0
    elif delta_e in Interval(0.5, 1.5, upper_closed=False):
        Q = 5.0 - (delta_e - 0.5)
    elif delta_e in Interval(1.5, 3.0, upper_closed=False):
        Q = 4.5 - ((delta_e - 1.5) / 1.5)
    elif delta_e in Interval(3.0, 6.0, upper_closed=False):
        Q = 3.5 - ((delta_e - 3.0) / 3.0)
    elif delta_e in Interval(6.0, 12.0, upper_closed=False):
        Q = 2.5 - ((delta_e - 6.0) / 6.0)
    elif delta_e in Interval(12.0, 24.0, upper_closed=False):
        Q = 1.0 - ((delta_e - 12.0) / 12.0)
    else:
        Q = 0.0
    return Q / 5.0


if __name__ == '__main__':
    pass
